package codegen;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.CtPrimitiveType;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import sf.codegen.IEntityEnhancer;
import sf.core.DBField;
import sf.core.DBObject;
import sf.core.IEntity;
import sf.spring.util.Assert;
import sf.spring.util.StringUtils;
import sf.tools.ArrayUtils;
import javassist.JavassistUtils;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class EnhanceTaskJavassistA {

    /**
     * 关闭生成设置值.
     */
    public static boolean genPushPull = false;

    private static String dbObjectClzPath = DBObject.class.getName().replace(".", "/");
    private static String dbFieldPath = DBField.class.getName().replace(".", "/");

    public EnhanceTaskJavassistA() {
    }

    /**
     * @param className
     * @return 返回null表示不需要增强，返回byte[0]表示该类已经增强，返回其他数据为增强后的class
     * @throws Exception
     */
    public byte[] doEnhance(ClassPool pool, String className) throws Exception {
        Assert.notNull(className, "");
        try {
            return enhanceClass(pool, className);
        } catch (EnhancedException e) {
            return ArrayUtils.EMPTY_BYTE_ARRAY;
        }
    }

    private static class EnhancedException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        @Override
        public synchronized Throwable fillInStackTrace() {
            return this;
        }
    }

    public byte[] enhanceClass(ClassPool pool, String className) throws Exception {

        byte[] b = null;
        // 获得要修改的类
        CtClass cc = pool.get(className);

        if (cc.isInterface()) {
            return null;
        }

        if (!cc.hasAnnotation(Table.class) && !cc.hasAnnotation(Entity.class)) {
            return null;
        }

        boolean isEntityInterface = isEntityClass(cc.getInterfaces(), cc.getSuperclass());
        if (!isEntityInterface) {
            return null;
        }

        byte[] sfd = cc.getAttribute(IEntityEnhancer.ENHANCE_HEADER);

        if (sfd != null && sfd.length > 0) {
            throw new EnhancedException();
        } else {
            // 设置修改标志
            sfd = new byte[]{0x1f};
            ClassFile cf = cc.getClassFile();
            AttributeInfo ai = new AttributeInfo(cc.getClassFile().getConstPool(), IEntityEnhancer.ENHANCE_HEADER, sfd);
            cf.addAttribute(ai);

            // 查找Field定义的字段
            List<String> enumFieldList = new ArrayList<String>();
            CtClass[] innerCC = cc.getDeclaredClasses();
            if (innerCC != null && innerCC.length > 0) {
                for (int i = 0; i < innerCC.length; i++) {
                    CtClass ctClass = innerCC[i];
                    CtClass[] interfeaces = ctClass.getInterfaces();
                    CtClass inter = null;
                    if (interfeaces != null && interfeaces.length > 0) {
                        inter = interfeaces[0];
                    }
                    if (ctClass.isEnum() && inter != null && inter.getName().contains(DBField.class.getName())) {
                        CtField[] declaredFields = ctClass.getDeclaredFields();
                        for (int j = 0; j < declaredFields.length; j++) {
                            CtField ctf = declaredFields[j];
                            enumFieldList.add(ctf.getName());
                        }
                    }
                }
            }


            // 设置方法需要的参数(自动查找父类的)
            List<CtField> ctFields = new ArrayList<>();
            CtField[] cfs = null;
            CtClass superClass = cc;
            while (superClass != null && !DBObject.class.getName().equals(superClass.getName())) {
                cfs = superClass.getDeclaredFields();
                ctFields.addAll(Arrays.asList(cfs));
                superClass = superClass.getSuperclass();
            }

            CtMethod[] ctms = cc.getDeclaredMethods();
            if (ctms != null && ctms.length > 0) {
                for (CtField ctf : ctFields) {
                    String mName = ctf.getName();
                    String mtName = StringUtils.capitalize(mName);
                    if (ctf.hasAnnotation(Column.class) || ctf.hasAnnotation(Id.class) || enumFieldList.contains(mName)) {
                        for (int i = 0; i < ctms.length; i++) {
                            CtMethod ctm = ctms[i];
                            if (ctm.getName().equals("set" + mtName) && !Modifier.isStatic(ctm.getModifiers())
                                    && enumFieldList.contains(mName)) {
                                boolean success = true;
                                try {
                                    String wrapper = "($w)$1";
                                    CtClass innct = ctm.getParameterTypes()[0];
                                    String wrapperName = "";
                                    if (innct.isPrimitive()) {
                                        // 是否是原始类型
                                        CtPrimitiveType ctpt = (CtPrimitiveType) innct;
                                        wrapperName = ctpt.getWrapperName();
                                        wrapper = wrapperName + ".valueOf($1)";
                                    }

//                                String body = "{if (this._recordUpdate) { prepareUpdate(" + cc.getName() + ".Field."
//                                        + ctf.getName() + ", " + wrapper + "); } this." + ctf.getName() + "=$1;}";
                                    // ctm.setBody(body);

                                    String src = "if (this._recordUpdate) {prepareUpdate(" + cc.getName() + ".Field."
                                            + ctf.getName() + ",  " + wrapper + ");}";
                                    ctm.insertBefore(src);
                                    break;
                                } catch (Exception e) {
                                    success = false;
                                }
                                if (!success) {
                                    String path = cc.getName().replace('.', '/');
                                    MethodInfo info = ctm.getMethodInfo2();
                                    Bytecode code = new Bytecode(info.getConstPool(), 4, 4);
                                    code.addAload(0);
                                    code.addGetfield(dbObjectClzPath, "_recordUpdate", "Z");
                                    code.addOpcode(Opcode.IFEQ);
                                    code.addAload(0);
                                    code.addGetstatic(path + "$Field", mName, "L" + path + "$Field");
                                    if (!JavassistUtils.setLoad(code, info.getDescriptor(), 1)) {
                                        code.addAload(1);
                                    }
                                    code.addInvokevirtual(dbObjectClzPath, "prepareUpdate", "(L" + dbFieldPath + ";Ljava/lang/Object;)V");
//                                    code.addAload(0);
//                                    code.addAload(1);
//                                    code.addPutfield(path, mName, JavassitUtils.getDesc(ctf.getDeclaringClass().toClass()));
//                                    code.addReturn(CtClass.voidType);
                                    CodeAttribute codeAttribute = info.getCodeAttribute();
                                    CodeIterator it = codeAttribute.iterator();
                                    it.insert(0,code.get());
                                }
                            }
                        }
                    } else {
                        boolean cascade = ctf.hasAnnotation(OneToMany.class) || ctf.hasAnnotation(ManyToOne.class) ||
                                ctf.hasAnnotation(ManyToMany.class) || ctf.hasAnnotation(OneToOne.class);
                        if (cascade) {
                            for (int i = 0; i < ctms.length; i++) {
                                CtMethod ctm = ctms[i];
                                if (ctm.getName().equals("set" + mtName) && !Modifier.isStatic(ctm.getModifiers())) {
                                    String src = "beforeSet(\"" + mName + "\");";
                                    ctm.insertBefore(src);
                                } else if (ctm.getName().equals("get" + mtName) && !Modifier.isStatic(ctm.getModifiers())) {
                                    String src = "beforeGet(\"" + mName + "\");";
                                    ctm.insertBefore(src);
                                }
                            }
                        }
                    }
                }
            }

            if (genPushPull) {
                GenPushPull(cc, ctFields, ctms);
            }

            b = cc.toBytecode();
            cc.detach();// 卸载
        }
        return b;
    }

    /**
     * 生成push,pull方法
     * @param cc
     * @param ctFields
     * @param ctms
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private void GenPushPull(CtClass cc, List<CtField> ctFields, CtMethod[] ctms) throws CannotCompileException, NotFoundException {

        boolean existPullMethod = false;
        boolean existPushMethod = false;
        for (int i = 0; i < ctms.length; i++) {
            CtMethod method = ctms[i];
            if (method.getName().equals("pull")) {
                existPullMethod = true;
            }
            if (method.getName().equals("push")) {
                existPushMethod = true;
            }
        }
        if (!existPullMethod) {
            // 生成pull方法
            StringBuilder sb = new StringBuilder("public Object pull(String fieldName) {\n");
            sb.append(" switch (fieldName.hashCode()) {\n");
            for (CtField f : ctFields) {
                if (!Modifier.isFinal(f.getModifiers()) && !Modifier.isStatic(f.getModifiers())) {
                    int hashCode = f.getName().hashCode();
                    sb.append("case ").append(hashCode).append(": \n ");
                    sb.append("if (fieldName.equals(\"").append(f.getName()).append("\")) {return this.").append(f.getName()).append(";} break;\n");
                }
            }
            sb.append(" default: return super.pull(fieldName);}\n");
            sb.append("return null;}");
            CtMethod method = CtNewMethod.make(sb.toString(), cc);
            cc.addMethod(method);
        }
        if (!existPushMethod) {
            // 生成push方法
            StringBuilder sb = new StringBuilder(" public boolean push(String fieldName, Object value) {\n");
            sb.append(" switch (fieldName.hashCode()) {\n");
            for (CtField f : ctFields) {
                if (!Modifier.isFinal(f.getModifiers()) && !Modifier.isStatic(f.getModifiers())) {
                    int hashCode = f.getName().hashCode();
                    sb.append("case ").append(hashCode).append(": \n ");
                    sb.append("if (fieldName.equals(\"").append(f.getName()).append("\")) {this.").append(f.getName()).append("=(").append(f.getType().getName()).append(")value;return true;} break;\n");
                }
            }
            sb.append(" default: return super.push(fieldName, value);}\n");
            sb.append("return false;}");
            CtMethod method = CtNewMethod.make(sb.toString(), cc);
            cc.addMethod(method);
        }
    }

    private boolean isEntityClass(CtClass[] interfaces, CtClass superName) {
        if (DBObject.class.getName().equals(superName.getName())) {
            // 绝大多数实体都是继承这个类的
            return true;
        }

        if (interfaces != null) {
            for (int i = 0; i < interfaces.length; i++) {
                CtClass ct = interfaces[i];
                if (IEntity.class.getName().equals(ct.getName())) {
                    return true;
                }
            }
        }

        if ("java.lang.Object".equals(superName.getName())) {
            return false;
        }
        // 递归检查父类
        try {
            return isEntityClass(superName.getInterfaces(), superName.getSuperclass());
        } catch (NotFoundException e) {
            return false;
        }
    }

    private static ClassLoader getLocaleClassLoader(URL path) throws Exception {
        // 实例化类加载器
        return new URLClassLoader(new URL[]{path});
    }
}
